/*
 * Copyright (C) 2012, EADS France
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.interpreter.backends.rdf;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import xowl.interpreter.backends.rete.Condition;
import xowl.interpreter.backends.rete.RETERule;
import xowl.interpreter.backends.rete.Token;
import xowl.interpreter.backends.rete.TokenActivable;

/**
 *
 * @author Laurent WOUTERS
 */
public class BufferedQueryEngine extends RDFQueryEngine {
    private static final int clearBufferSize = 300;
    private static final int clearThreshold = 5;
    
    private Query head;
    private Query tail;
    private int size;
    
    public BufferedQueryEngine(RDFRepository repo) {
        super(repo);
    }
    
    @Override protected xowl.lang.runtime.Value eval_Contains(xowl.interpreter.impl.Evaluator parent, xowl.lang.actions.Contains query) throws xowl.interpreter.XOWLException {
        if (query.getAllAxioms().size() == 1 && query.getAllAxioms().iterator().next() instanceof xowl.lang.actions.FunctionDefinition)
            return contains_FunctionDefinition(parent, repository, (xowl.lang.actions.FunctionDefinition)query.getAllAxioms().iterator().next());
        Translation translation = repository.getXOWLTranslator().translateAxioms(parent, true, true, query.getAllAxioms());
        applyChanges();
        Iterator<Query> iter = getQueries();
        while (iter.hasNext()) {
            Query q = iter.next();
            if (q.matches(translation.getTriples()))
                return xowl.utils.Values.getValue(repository, q.hasSolutions(parent, query));
        }
        Query q = addQuery(translation.getTriples());
        return xowl.utils.Values.getValue(repository, q.hasSolutions(parent, query));
    }
    
    @Override public List<xowl.interpreter.QuerySolution> select(xowl.interpreter.impl.Evaluator parent, xowl.lang.actions.Select query) throws xowl.interpreter.XOWLException {
        if (query.getAllAxioms().size() == 1 && query.getAllAxioms().iterator().next() instanceof xowl.lang.actions.FunctionDefinition)
            return select_FunctionDefinition(parent, repository, (xowl.lang.actions.FunctionDefinition)query.getAllAxioms().iterator().next());
        Translation translation = repository.getXOWLTranslator().translateAxioms(parent, true, true, query.getAllAxioms());
        applyChanges();
        Iterator<Query> iter = getQueries();
        while (iter.hasNext()) {
            Query q = iter.next();
            if (q.matches(translation.getTriples()))
                return q.getSolutions(parent, query, translation.getTriples());
        }
        Query q = addQuery(translation.getTriples());
        return q.getSolutions(parent, query, translation.getTriples());
    }
    
    private Iterator<Query> getQueries() {
        if (size >= clearBufferSize) {
            Query current = tail;
            int counter = 0;
            while (current.previous != null && current.previous.hitCount <= clearThreshold) {
                current = current.previous;
                counter++;
            }
            if (current.previous != null)
                current.previous.next = null;
            tail = current;
            size -= counter;
        }
        return new Iterator<Query>() {
            private Query current = head;
            @Override public boolean hasNext() { return (current != null); }
            @Override public Query next() {
                Query temp = current;
                current = current.next;
                return temp;
            }
            @Override public void remove() {  }
        };
    }
    
    private Query addQuery(Collection<RDFTriple> triples) {
        Query query = new Query(triples);
        RETERule rule = new RETERule(query);
        for (RDFTriple triple : triples)
            rule.getPositives().add(new Condition(null, triple));
        applyChanges();
        network.addRule(rule);
        if (head == null) {
            head = query;
            tail = query;
        } else {
            tail.next = query;
            query.previous = tail;
            tail = query;
        }
        size++;
        return query;
    }
    
    private class Query implements TokenActivable {
        private Query previous;
        private Query next;
        private List<RDFTriple> triples;
        private List<Token> tokens;
        private int hitCount;
        
        public Collection<RDFTriple> getTriples() { return triples; }
        
        public Query(Collection<RDFTriple> triples) {
            this.triples = new ArrayList<RDFTriple>(triples);
            this.tokens = new ArrayList<Token>();
            this.hitCount = 0;
        }
        
        public void onHit() {
            hitCount++;
            while (previous != null && previous.hitCount < hitCount) {
                Query before = previous;
                Query after = next;
                this.previous = before.previous;
                this.next = before;
                before.previous = this;
                before.next = after;
                if (after != null) after.previous = before;
                else tail = before;
                if (this.previous != null) this.previous.next = this;
                else head = this;
            }
            if (previous == null)
                head = this;
        }
        
        @Override public void activateToken(Token token) { tokens.add(token); }
        @Override public void deactivateToken(Token token) { tokens.remove(token); }
        @Override public void activateTokens(Collection<Token> tokens) {
            for (Token token : tokens)
                this.tokens.add(token);
        }
        @Override public void deactivateTokens(Collection<Token> tokens) {
            for (Token token : tokens)
                this.tokens.remove(token);
        }
        
        public boolean matches(Collection<RDFTriple> candidate) {
            if (candidate.size() != triples.size())
                return false;
            Iterator<RDFTriple> i1 = triples.iterator();
            Iterator<RDFTriple> i2 = candidate.iterator();
            while (i1.hasNext())
                if (!i1.next().equals(i2.next()))
                    return false;
            onHit();
            return true;
        }
        
        public boolean hasSolutions(xowl.interpreter.impl.Evaluator parent, xowl.lang.actions.Query query) {
            if (tokens.isEmpty())
                return false;
            for (Token token : tokens)
                if (verifiesConditions(parent, query, token.getBindings()))
                    return true;
            return false;
        }
        
        public List<xowl.interpreter.QuerySolution> getSolutions(xowl.interpreter.impl.Evaluator parent, xowl.lang.actions.Query query, Collection<RDFTriple> originals) throws xowl.interpreter.XOWLException {
            Map<String, xowl.lang.actions.QueryVariable> vars = new HashMap<String, xowl.lang.actions.QueryVariable>();
            for (RDFTriple triple : originals) {
                build(vars, triple.getObject());
                build(vars, triple.getProperty());
                build(vars, triple.getSubject());
            }
            List<xowl.interpreter.QuerySolution> results = new ArrayList<xowl.interpreter.QuerySolution>();
            for (Token t : tokens) {
                if (verifiesConditions(parent, query, t.getBindings()))
                    results.add(getSolution(t, vars));
            }
            return results;
        }
        private void build(Map<String, xowl.lang.actions.QueryVariable> map, RDFNode node) throws xowl.interpreter.XOWLException {
            if (node.getNodeType() == RDFNodeType.Variable) {
                XOWLVariableNode vnode = (XOWLVariableNode)node;
                String name = vnode.getVariable().getName();
                if (!map.containsKey(name))
                    map.put(name, vnode.getVariable());
                else if (map.get(name) != vnode.getVariable())
                    throw new xowl.interpreter.XOWLException("Multiple variables with the same name in the query");
            }
        }
        private xowl.interpreter.QuerySolution getSolution(Token token, Map<String, xowl.lang.actions.QueryVariable> vars) throws xowl.interpreter.XOWLException {
            Map<XOWLVariableNode, RDFNode> bindings = token.getBindings();
            Map<xowl.lang.actions.QueryVariable, xowl.lang.runtime.Value> data = new HashMap<xowl.lang.actions.QueryVariable, xowl.lang.runtime.Value>();
            for (Entry<XOWLVariableNode, RDFNode> entry : bindings.entrySet())
                data.put(vars.get(entry.getKey().getVariable().getName()), repository.getValue(entry.getValue()));
            return new xowl.interpreter.QuerySolution(data);
        }
        
        private boolean verifiesConditions(xowl.interpreter.impl.Evaluator parent, xowl.lang.actions.Query query, Map<XOWLVariableNode, RDFNode> bindings) {
            if (query.getAllGuards().isEmpty())
                return true;
            xowl.interpreter.impl.AggregatedEvaluator evaluator = (xowl.interpreter.impl.AggregatedEvaluator)parent;
            QueryVariableEvaluator qve = new QueryVariableEvaluator(repository, bindings);
            evaluator.addPart(qve);
            for (xowl.lang.owl2.LiteralExpression guard : query.getAllGuards()) {
                try {
                    if (!xowl.utils.Values.isTrue(evaluator.evalLiteral(evaluator, guard))) {
                        evaluator.removePart(qve);
                        return false;
                    }
                } catch (xowl.interpreter.XOWLException ex) {
                    evaluator.removePart(qve);
                    return false;
                }
            }
            evaluator.removePart(qve);
            return true;
        }
    }
}
